package com.juma.cartridge.toker;

import java.util.UUID;

import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.util.Pool;

/**
 * 基于redis中间件的token生成&消费客户端实现.
 * [重要说明]:
 * 该类的唯一构造参数要求传入Redis连接池Pool对象.
 * 请注意在构建pool连接池时,各系统使用redis的database序号均采用默认0
 * 如果某个系统构建的pool单独指定了redis database序号(非默认),则会产生token生产端和消费端不对称的情况.
 * 导致调用方生成的token传到被调用方时无法消费成功,进而影响业务执行.
 * @ClassName RedisTokenClient
 * @Description
 * @Author zhuangguangyou
 * @Date 2019年11月15日 上午9:09:43
 * @Version 1.0
 *
 */
@Slf4j
public class RedisTokenClient implements TokenClient {

    private Pool<Jedis> jedisPool;
    
//    private TokenPool tokenPool;
    
    public static final int maxTimeout = 3600*24*30; //允许的token最大超时时间30天
    
    /**
     * 该类的唯一构造参数要求传入Redis连接池Pool对象.
     * 请注意在构建pool连接池时,各系统使用redis的database序号均采用默认0
     * 如果某个系统构建的pool单独指定了redis database序号(非默认),则会产生token生产端和消费端不对称的情况.
     * 导致调用方生成的token传到被调用方时无法消费成功,进而影响业务执行.
     * @param pool
     */
    public RedisTokenClient(Pool<Jedis> pool) {
        this.jedisPool = pool;
//        this.tokenPool = new TokenPool(pool,10);
    }
    
    
    @Override
    public Token produce(int timeoutInSec) {
        return produce(null,timeoutInSec);
    }

    
    @Override
    public Token produce(String name, int timeoutInSec) {
        
        timeoutInSec = resetTimeout(timeoutInSec);
        //先从缓冲池拿token
        
//        Token tk = tokenPool.get(name, timeoutInSec);
//        if(tk!=null) {
//            return tk;
//        }
        
        //缓冲池没有,则即时生成token
        String id = UUID.randomUUID().toString().replaceAll("-", "");
        Token token = new Token(name,id,timeoutInSec);
        ApplyUtils.apply(jedisPool, token);
        return token;
    }

    
    @Override
    public Token produce(String name, String tokenId, int timeoutInSec) {
        if(tokenId==null||tokenId.isEmpty()) {
            throw new IllegalArgumentException("tokenId不能为空");
        }
        timeoutInSec = resetTimeout(timeoutInSec);
        Token token = new Token(name,tokenId,timeoutInSec);
        ApplyUtils.apply(jedisPool, token);
        return token;
    }
    
    
    @Override
    public boolean consume(String tokenId) {
        return consume(null,tokenId);
    }


    @Override
    public boolean consume(String name, String tokenId) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            String key = getKey(name,tokenId);;
            return jedis.del(key) > 0;
        }finally {
            if(jedis!=null) {
                jedis.close();
            }
        }
    }
    
    @Override
    public boolean check(String tokenId) {
        return check(null,tokenId);
    }


    @Override
    public boolean check(String name, String tokenId) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            String key = getKey(name,tokenId);    
            return jedis.exists(key);
        }finally {
            if(jedis!=null) {
                jedis.close();
            }
        }
    }

    private String getKey(String name,String tokenId) {
        String key = Token.prefix;
        if(name!=null && !name.trim().isEmpty()) {
            key = key.concat(name).concat(".");
        }
        key = key.concat(tokenId);
        return key;
    }
    
    private int resetTimeout(int timeoutInSec) {
        if(timeoutInSec > maxTimeout) {
            throw new IllegalArgumentException("token最大有效期不能超过30天("+maxTimeout+"秒)");
        }
        //如果timeout敲定为-1,表明调用者希望此token长期存活,但考虑到会产生token垃圾(无用token长期驻留内存)
        //因此这里将-1对应的timeout时间最大限定为30天.30天后token仍没消费,说明是token垃圾,redis会自动清除.
        //PS:对于token的应用场景,极少有需要token驻留30天以上的情况.就算有,也是很不合理的.
        return timeoutInSec<1?maxTimeout:timeoutInSec;
    }
    
}
